﻿/**************************************************************************
创建日期:	2019/6/19
作	  者:	张存
邮 	  箱:	zhangcunliang@126.com
创建时间:	
Copyright (c)  ZhCun.cn

描	述：实现Action的加密，日志记录，异常处理的过滤
       注意符合规范 - 接口实现前后顺序为执行顺序，异常仅能捕获action的异常
记	录：
***************************************************************************/
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Internal;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using Newtonsoft.Json;
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using ZhCun.Utils;
using ZhCun.Utils.Helpers;
using ZhCun.WebCommon.AL;
using ZhCun.WebCommon.DBModels;
using ZhCun.WebCommon.FormatJson;
using ZhCun.WebCommon.WebApi;

namespace ZhCun.WebCommon
{
    public class CommonActionFilterAttribute : Attribute, IExceptionFilter, IActionFilter, IResourceFilter
    {
        static CommonActionFilterAttribute()
        {
            WriteWebApiLog = AppCommonConfig.AppSettings.WriteHttpLog;
            if (!string.IsNullOrWhiteSpace(AppCommonConfig.AppSettings.RsaPrivateKey))
                _RSA = new RSAHelper(AppCommonConfig.AppSettings.RsaPrivateKey);
        }

        #region 日志成员

        private readonly static bool WriteWebApiLog;

        TSysLogHttp _Log;

        /// <summary>
        /// 日志记录器
        /// </summary>
        private Stopwatch Stopwatch;

        #endregion

        #region 加密成员

        /// <summary>
        /// 加密Key
        /// </summary>
        private string AesKey;
        /// <summary>
        /// 本次请求是否需要加密的标记
        /// </summary>
        private bool IsEncrypt = false;
        /// <summary>
        /// RSA加密对象
        /// </summary>
        private readonly static RSAHelper _RSA;

        #endregion

        /// <summary>
        /// 使用MemoryStream读取Request Body内容
        /// </summary>
        protected virtual string ReadRequestBodyAsString(HttpContext context)
        {
            long contentLen = context.Request.ContentLength == null ? 0 : context.Request.ContentLength.Value;
            if (contentLen > 0)
            {
                context.Request.EnableRewind();
                byte[] buffer = new byte[contentLen];
                context.Request.Body.Read(buffer, 0, buffer.Length);
                var bodyStr = System.Text.Encoding.UTF8.GetString(buffer);
                context.Request.Body.Position = 0;
                return bodyStr;
            }
            else
            {
                return null;
            }
        }

        private void WriteRequestBody(HttpContext context)
        {
            if (WriteWebApiLog)
            {
                _Log.RequestContent = ReadRequestBodyAsString(context);
            }
        }

        public string Flag { set; get; }

        /// <summary>
        /// 处理加密，日中中的请求body赋值
        /// </summary>
        public void OnResourceExecuting(ResourceExecutingContext context)
        {
            if (WriteWebApiLog)
            {
                _Log = new TSysLogHttp();
                _Log.RequestPath = context.HttpContext.Request.Path.Value;
                _Log.HttpType = 0;
                _Log.AddHost = context.HttpContext.GetRemoteIp();
                _Log.HttpMethod = context.HttpContext.Request.Method;
                _Log.RequestParam = context.HttpContext.Request.QueryString.ToString();
                Stopwatch = new Stopwatch();
                Stopwatch.Start();
            }
            ControllerActionDescriptor action = context.ActionDescriptor as ControllerActionDescriptor;
            //如果标记未加密
            if (action.MethodInfo.GetCustomAttributes(typeof(NonEncryptAttribute), false).Any())
            {
                IsEncrypt = false;
                WriteRequestBody(context.HttpContext);
                return;
            }
            //使用rsa加密的aes key
            var aesEncryptKey = context.HttpContext.GetHeader(Constants.DATA_ENCRYPT_KEY_VALUE);
            if (string.IsNullOrWhiteSpace(aesEncryptKey))
            {
                IsEncrypt = false;
                if (AppCommonConfig.AppSettings.DataEncrypt)
                {
                    context.Result = new ContentResult
                    {
                        StatusCode = StatusCodes.Status400BadRequest,
                        Content = "data format error"
                    };
                }
                WriteRequestBody(context.HttpContext);
                //密钥为空不进行加解密
                return;
            }
            if (_RSA == null)
            {
                IsEncrypt = false;
                return;
            }
            try
            {
                IsEncrypt = true;
                AesKey = _RSA.Decrypt(aesEncryptKey);
                //如果get请求而且配置了加密，那么请求内容不解密，返回内容进行加密
                if (context.HttpContext.Request.Method == "POST")
                {
                    var enBodyStr = ReadRequestBodyAsString(context.HttpContext);
                    var bodyBytes = DEncryptAES.DecryptBytes(enBodyStr, AesKey);
                    context.HttpContext.Request.Body = new MemoryStream(bodyBytes);
                    if (_Log != null)
                    {
                        _Log.RequestContent = UTF8Encoding.UTF8.GetString(bodyBytes).Replace("\0", "");
                    }
                }
            }
            catch (Exception ex)
            {
                if (_Log != null)
                {
                    _Log.Remark = "解密请求内容发生异常";
                }
                LogHelper.LogObj.Error($"解密请求内容发生异常,aesKey:{aesEncryptKey}", ex);
                throw ex;
            }
        }

        public void OnActionExecuting(ActionExecutingContext context)
        { }

        /// <summary>
        /// 自定义异常处理
        /// </summary>
        public void OnException(ExceptionContext context)
        {
            LogHelper.LogObj.Error("全局过滤异常", context.Exception);
            context.Result = new ContentResult
            {
                StatusCode = 500,
                ContentType = "text/json;charset=utf-8;",
                Content = "服务器发生异常,详情请查看日志"
            };
        }

        public void OnActionExecuted(ActionExecutedContext context)
        {
            if (context.Result is ObjectResult rObj && rObj.Value != null)
            {
                if (IsEncrypt)
                {
                    var responseBody = rObj.Value.ToJson();
                    var enResult = DEncryptAES.Encrypt(responseBody, AesKey);
                    rObj.Value = enResult;
                    if (_Log != null) _Log.ResponseContent = responseBody;
                }
            }
            if (WriteWebApiLog)
            {
                if (context.Controller is IAuthorizeController controller)
                {
                    _Log.AddUserId = controller.LoginToken?.LoginUserId;
                    _Log.UserName = controller.LoginToken?.UserName;
                    _Log.ClientSN = controller.LoginToken?.LoginClientSN;
                    _Log.LoginName = controller.LoginToken?.LoginName;
                }
                if (context.ActionDescriptor is ControllerActionDescriptor action)
                {
                    _Log.ControllerName = action.ControllerName;
                    _Log.ActionName = action.ActionName;
                }
            }
        }

        public void OnResourceExecuted(ResourceExecutedContext context)
        {
            if (WriteWebApiLog)
            {
                _Log.StatusCode = context.HttpContext.Response.StatusCode;
                if (string.IsNullOrWhiteSpace(_Log.ResponseContent))
                {
                    if (context.Result is ObjectResult rObj && rObj.Value != null)
                    {
                        _Log.ResponseContent = rObj.Value.ToJson();
                    }
                }
                SaveLog();
            }
        }

        private void SaveLog()
        {
            if (WriteWebApiLog)
            {
                Stopwatch.Stop();
                _Log.RunTime = (decimal)Stopwatch.Elapsed.TotalSeconds;
                DBLog.WriteLogHttp(_Log);
            }
        }
    }
}